这是JS 原生方法原理探究系列的第七篇文章。本文会介绍如何实现 Object.assign()
方法。
Object.assign()
的基本用法
要实现 Object.assign()
,首先了解它的大概用法:
- 接受的第一个参数表示目标对象(浅拷贝的结果),如果是 null 或者 undefined,直接报错;如果是对象字面量或者数组,直接使用;如果是基本类型,则装箱为对应的对象。
- 如果只接受了第一个参数,则将其包装为对象直接返回;如果不止接受了第一个参数,比如说接受了第二,第三 …… 等多个参数,那么这些参数表示源对象,它们的自身可枚举属性会一一添加到目标对象上,属性同名则以靠后的对象为准,进行属性覆盖。
- 第一个参数往后的参数,如果是 null 或者 undefined,那么直接跳过;其余的情况则尝试找出它们的可枚举属性,但实际上,只有字符串、数组、对象字面量这些类型是具有可枚举属性的。
实现代码
根据上面所讲的思路,实现的代码就是下面这样的:
function myAssign(target,...objs){
if(target === null || target === undefined){
throw new TypeError("can not convert null or undefined to object")
}
let res = Object(target)
objs.forEach(obj => {
'use strict'
if(obj != null && obj != undefined){
for(let key in obj){
if(Object.prototype.hasOwnProperty.call(obj,key)){
res[key] = obj[key]
}
}
}
})
return res
}
Object.defineProperty(Object,'myAssign',{
value: myAssign,
writable: true,
configurable: true,
enumerable: false
})
需要注意的要点
需要注意的要点如下:
为什么不直接通过 .
给 Object 添加 myAssign
方法?
Object.myAssign()
实际上是 Object 的一个静态方法,但是不要直接通过 .
添加,因为这种方式添加的方法是可以枚举的,而 assign()
方法不可枚举。所以这里使用 Object.defineProperty()
添加,同时设置该方法不可枚举、可读、可配置。
为什么要使用严格模式?
考察参数出现字符串的情况。下面这两种情况容易理解:
Object.assign({a:1},"cd")
// 把 "cd" 的可枚举属性 0 和 1 添加到目标对象上,最后得到 {a:1,0:“c”,1:"d"}
Object.assign("cd",{a:1})
// 把 {a:1} 的可枚举属性 a 添加到目标对象上,最后得到 String{“cd”,a:1}
但如果是这种情况:
Object.assign("ab","cd")
// 报错 Cannot assign to read only property '0' of object '[object String]'
这里尝试把 “cd”
的可枚举属性 0 和 1 添加到目标对象上,但问题是,目标对象 String{“ab”}
也有可枚举属性 0 和 1,而且是只读的,这意味着我们尝试去修改目标对象的只读属性,所以报错也就很合理了。但是,在非严格模式下,这种行为只会静默失败,为了让它真的抛出错误,必须声明使用严格模式。
为什么不使用 Reflect.ownKeys(obj)
?
考虑目标对象和源对象都是数组的情况,使用 Reflect.ownKeys(obj)
确实可以一次性获得 obj 的自身可枚举属性,但是这些属性除了数组索引之外,也包含数组长度,这会导致将源对象的数组长度作为目标对象的数组长度,但实际上,两者长度不一定相等。比如,Objetc.myAssign([1,2,3],[8,9])
的结果将不是期望得到的 [8,9,3]
,而是 [8,9]
,因为目标对象的长度被覆盖了。
为什么不直接使用 obj.hasOwnProperty(key)
?
既然不能使用 Reflect.ownKeys(obj)
,那么就只有先使用 for(let key in obj)
获得 obj 的自身属性 + 原型链属性,再使用 obj.hasOwnProperty(key)
筛选出自身属性了。但是为什么不直接使用 obj.hasOwnProperty(key)
呢?
这是因为,我们对源对象的情况并不了解。一方面,它可能重写了 hasOwnProperty
方法;另一方面,它可能是基于 Object.create(null)
构建的,这样的对象不会从 Object 原型上继承 hasOwnProperty
方法。所以这里借用 Object 原型的 hasOwnProperty
方法,是最保险的方式。